Frequently Asked Questions

What does $(type1,type2) mean? What does $(expr1, expr2) mean?

Cyclone has tuples, which are anonymous structs with fields numbered 0, 1, 2, …. For example, $(int,string_t) is a pair of an int and a string_t. An example value of this type is $(4,”cyclone”). To extract a field from a tuple, you use array-like notation: you write x[0], not x.0.

What does int @ mean?

In Cyclone @ is a pointer that is guaranteed not to be NULL. The Cyclone compiler guarantees this through static or dynamic checks. For example,

  int *x = NULL;

is not an error, but

  int @x = NULL;

is an error. Note that “int @” is shorthand for the more verbose “int *@notnull”.

What does int *{37} mean?

This is the type of (possibly-null) pointers to a sequence of at least 37 integers, which can also be written as “int *@numelts(37)”. The extra length information is used by Cyclone to prevent buffer overflows. For example, Cyclone will compile x[expr] into code that will evaluate expr, and check that the result is less than 37 before accessing the element. Note that int * is just shorthand for int *{1}. Currently, the expression in the braces must be a compile-time constant.

What does int *`r mean?

This is the type of a pointer to an int in region `r. This can also be written as “int *@region(`r)”. A region indicates conceptually where in memory an object is stored; different regions have different lifetimes and deallocation strategies, and the aliasing into certain regions may be restricted. Cyclone uses this region information to prevent dereferencing a pointer whose storage has been deallocated. See Memory Management Via Regions for more information on regions.

What does `H mean?

This is Cyclone’s heap region: objects in this region cannot be explicitly freed, only garbage-collected. Effectively, this means that pointers into the heap region can always be safely dereferenced.

What does int @{37}`r mean?

A pointer can come with all or none of the nullity, bound, and region annotation. This type is the type of non-NULL pointers to at least 37 consecutive integers in region `r. When the bound is omitted it default to 1.

What is a pointer type’s region when it’s omitted?

Every pointer type has a region; if you omit it, the compiler chooses a region name you implicitly. The name chosen depends on where the pointer type occurs. In function arguments, a fresh region variable is used. In function results and type definitions (including typedef), the heap region (`H) is used. In function bodies, the compiler looks at the uses (using unification) to try to determine a region. See Regions for more information.

What does int ? mean?

The ? a special kind of pointer that carries along bounds information. It is a “questionable” pointer: it might be NULL or pointing out of bounds. An int ? is a pointer to an integer, along with some information that allows Cyclone to check whether the pointer is in bounds at run-time. These are the only kinds of pointers that you can use for pointer arithmetic in Cyclone.

What does `a mean?

`a is a type variable. Type variables are typically used in polymorphic functions. For example, if a function takes a parameter of type `a, then the function can be called with a value of any suitable type. If there are two arguments of type `a, then any call will have to give values of the same type for those parameters. And if the function returns a type `a, then it must return a result of the same type as the the argument. Syntactically, a type variable is any identifier beginning with ` (backquote).

What is a “suitable” type for a type variable?

The last question said that a type variable can stand for a “suitable” type. Unfortunately, not all types are “suitable.” Briefly, the “suitable” types are those that fit into a general-purpose machine register, typically including int, and pointers. Non-suitable types include float, struct types (which can be of arbitrary size), tuples, and questionable pointers. Technically, the suitable types are the types of “box kind,” described below.

How do I cast from void *?

You can’t do this in Cyclone. A void * in C really does not point to void, it points to a value of some type. However, when you cast from a void * in C, there is no guarantee that the pointer actually points to a value of the expected type. This can lead to crashes, so Cyclone doesn’t permit it. Cyclone’s polymorphism and tagged unions can often be used in places where C needs to use void *, and they are safe. Note that you can generally cast to a void * in Cyclone, you just won’t be able to cast back.

What does _ (underscore) mean in types?

Underscore is a “wildcard” type. It stands for some type that the programmer doesn’t want to bother writing out; the compiler is expected to fill in the type for the programmer. Sometimes, the compiler isn’t smart enough to figure out the type (you will get an error message if so), but usually there is enough contextual information for the compiler to succeed. For example, if you write

  _ x = new Pair(3,4);

the compiler can easily infer that the wildcard stands for struct Pair @. In fact, if x is later assigned NULL, the compiler will infer that x has type struct Pair * instead.

Note that only in restricted cases is _ allowed as part of top-level declarations.

What do `a::B, `a::M, `a::A, `a::R, `a::Q and `a::E mean?

Types are divided into different groups, which we call kinds. There are six kinds: B (for Box), M (for Memory), A (for Any), E (for Effect), R (for Region), and Q (for alias-Qualifier). The notation typevar::kind says that a type variable belongs to a kind. A type variable can only be instantiated by types that belong to its kind.

Box types include int, long, region_t, tag_t, enums, and non-@fat pointers. Memory types include all box types, void, char, short, long long, float, double, arrays, tuples, datatype and @extensible datatype variants, @fat pointers, and non-abstract structs and unions. Any types include all types that don’t have kind R, E or Q. For the region types, R indicates regions like the heap, stack, and dynamic regions; Q indicates alias qualifiers like ALIASABLE, UNIQUE, REFCNT or RESTRICTED; Effect types are sets of regions (these are explained elsewhere).

What does it mean when type variables don’t have explicit kinds?

Every type variable has a kind, but usually the programmer doesn’t have to write it down. In function prototypes, the compiler will infer the most permissive kind. For example,

  void f(`a *`b x, `c * y, `a z);

is shorthand for

void f(`a::B *`b::R x, `c::M * y, `a::B z)

In type definitions, no inference is performed: an omitted kind is shorthand for ::B. For example,

struct S<`a,`r::R> { `a *`r x; };

is shorthand for

struct S<`a::B,`r::R> { `a *`r x;};

but

struct S<`a,`r>{`a *`r x;};

is not.

What does struct List<`a,`r::R> mean?

struct List takes a type of box kind and a region and produces a type. For example, struct List<int, `H> is a type, and struct List<struct List<int,`H>@, `H> is a type. struct List<`a,`r::R> is a list whose elements all have type `a and live in region `r.

What is a @tagged union?

In C, when a value has a union type, you know that in fact it has one of the types of the union’s fields, but there is no guarantee which one. This can lead to crashes in C. Cyclone’s @tagged unions are like C unions with some additional information (a tag) that lets the Cyclone compiler determine what type the underlying value actually has, thus helping to ensure safety.

What is “abstract”?

abstract is a storage-class specifier, like static or extern. When attached to a top-level type declaration, it means that other files can use the type but cannot look at the internals of the type (e.g., other files cannot access the fields of an abstract struct). Otherwise, abstract has the same meaning as the auto (default) storage class. Hence abstract is a way to state within a Cyclone file that a type’s representation cannot be exported.

What are the Cyclone keywords?

In addition to the C keywords, the following have special meaning and cannot be used as identifiers: abstract, catch, datatype, fallthru, let, malloc, namespace, new, NULL, region_t, regions, rmalloc, rnew, throw, try, using. As in gcc, attribute is reserved as well.

What are “namespace” and “using”?

These constructs provide a convenient way to help avoid name clashes. namespace X prepends X:: to the declarations in its body (rest of file in case of namespace X;) and using X makes the identifiers prepended with X:: available without having to write the X::.

What is “fallthru”?

In Cyclone, you cannot implicitly fall through from one switch case to the next (a common source of bugs in C). Instead, you must explicitly fall through with a fallthru statement. So, to port C code, place fallthru; at the end of each case that implicitly falls through; note that fallthru may not appear in the last case of a switch.

fallthru is useful for more than just catching bugs. For instance, it can appear anywhere in a case; its meaning is to immediately goto the next case. Second, when the next case of the switch has pattern variables, a fallthru can (and must) be used to specify expressions that will be bound to those variables in the next case. Hence fallthru is more powerful (but more verbose) than “or patterns” in ML.

What is “new”?

new expr allocates space in the heap region, initializes it with the result of evaluating expr, and returns a pointer to the space. It is roughly equivalent to

 type @temp = malloc(sizeof(type));
 *temp = expr;

where type is the type of expr. You can also write

  new { for i < expr1 : expr2 }

to heap-allocate an array of size expr1 with the ith element initialized to expr2 (which may mention i).

How do I use tuples?

A tuple type is written $(type0, …, typen). A value of the type is constructed by $(expr0, …, exprn), where expri has type typei. If expr has type $(type0, …, typen), you can extract the component i using expr[i]. The expression in the brackets must be a compile-time constant. In short, tuples are like anonymous structs where you use expr[i] to extract fields instead of expr.i. There is no analogue of the -> syntax that can be used with pointers of structs; if expr has type $(type1, …, typen) *, you can extract component i by (*expr)[i].

What is { for i < expr1 : expr1 }?

This is an array initializer. It can appear where array initializers appear in C, and it can appear as the argument to new. It declares an identifier (in this case, i) whose scope is expr2. expr1 is an expression which is evaluated to an unsigned integer giving the desired size of the array. The expression expr2 is evaluated expr1 times, with i ranging over 0, 1, …, expr1-1; the result of each evaluation initializes the ith element of the array.

The form new {for i < expr1 : expr2} allocates space for a new array and initializes it as just described. This form is the only way to create arrays whose size depends on run-time information. When {for i < expr1 : expr2} is not an argument to new, expr1 must be constant and expr2 may not mention i. This restriction includes all uses at top-level (for global variables).

How do I throw and catch exceptions?

A new exception is declared as in

  datatype exn { MyExn };

The exception can be thrown with the statement

  throw MyExn;

You can catch the expression with a try/catch statement:

  try statement1 catch { case MyExn: statement2 }

If statement1 throws an MyExn and no inner catch handles it, control transfers to statement2.

The catch body can have any number of case clauses. If none match, the exception is re-thrown.

Exceptions can carry values with them. For example, here’s how to declare an exception that carries an integer:

  datatype exn { MyIntExn(int) };

Values of such exceptions must be heap-allocated. For example, you can create and throw a MyIntExn exception with

  throw new MyIntExn(42);

To catch such an exception you must use an &-pattern:

  try statement1
  catch {
    case &MyIntExn(x): statement2
  }

When the exception is caught, the integer value is bound to x.

The exn type is just a pre-defined @extensible datatype type. Therefore, all the standard rules for extending, creating objects, and destructing objects of a datatype apply.

How efficient is exception handling?

Entering a try block is implemented using setjmp. Throwing an exception is implemented with longjmp. Pattern-matching a datatype against each case variant in the catch clause is a pointer-comparsion. In short, exception handling is fairly lightweight.

What does “let” mean?

In Cyclone, let is used to declare variables. For example,

  let x,y,z;

declares the three variables x, y, and z. The types of the variables do not need to be filled in by the programmer, they are filled in by the compiler’s type inference algorithm. The let declaration above is equivalent to

  _ x;
  _ y;
  _ z;

There is a second kind of let declaration, with form

  let pattern = expr;

It evaluates expr and matches it against pattern, initializing the pattern variables of pattern with values drawn from expr. For example,

  let x = 3;

declares a new variable x and initializes it to 3, and

  let $(y,z) = $(3,4);

declares new variables y and z, and initializes y to 3 and z to 4.

What is a pattern and how do I use it?

Cyclone’s patterns are a convenient way to destructure aggregate objects, such as structs and tuples. They are also the only way to destructure datatypes. Patterns are used in Cyclone’s let declarations, switch statements, and try/catch statements.

What does _ mean in a pattern?

It is a wildcard pattern, matching any value. For example, if f is a function that returns a pair, then

  let $(_,y) = f(5);

is a way to extract the second element of the pair and bind it to a new variable y.

What does it mean when a function has an argument with type `a?

Any type that looks like \(backquote) followed (without whitespace) by an identifier is a type variable. If a function parameter has a type variable for its type, it means the function can be called with any pointer or with an int. However, if two parameters have the same type variable, they must be instantiated with the same type. If all occurrences of \a appear directly under pointers (e.g., `a *), then an actual parameter can have any type, but the restrictions about using the same type still apply. This is called parametric polymorphism, and it’s used in Cyclone as a safe alternative to casts and void *.

Do functions with type variables get duplicated like C++ template functions? Is there run-time overhead for using type variables?

No and no. Each Cyclone function gives rise to one function in the output, and types are not present at run-time. When a function is called, it does not need to know the types with which the caller is instantiating the type variables, so no instantiation actually occurs—the types are not present at run-time. We do not have to duplicate the code because we either know the size of the type or the size does not matter. This is why we don’t allow type variables of memory kind as parameters—doing so would require code duplication or run-time types.

Can I use varargs?

Yes, Cyclone has a way of supporting variable-argument functions. It is not quite the same as C’s, but it is safe. For instance, we have written type-safe versions of printf and scanf all within Cyclone. See Varargs for more information.

Why can’t I declare types within functions?

We just haven’t implemented this support yet. For now, you need to hoist type declarations and typedefs to the top-level.

What casts are allowed?

Cyclone doesn’t support all of the casts that C does, because incorrect casts can lead to crashes. Instead, Cyclone supports a safe subset of C’s casts. Here are some examples.

All of C’s numeric casts, conversions, and promotions are unchanged.

You can always cast between type@{const-expr}, type*{const-expr}, and type ?. A cast from type ? to one of the other types includes a run-time check that the pointer points to a sequence of at least const-expr objects. A cast to type@{const-expr}from one of the other types includes a run-time check that the pointer is not NULL. No other casts between these type have run-time checks. A failed run-time check throws Null_Exception. A pointer into the heap can be cast to a pointer into another region. A pointer to a struct or tuple can be cast to a pointer to another struct or tuple provided the “target type” is narrower (it has fewer fields after “flattening out” nested structs and tuples) and each (flattened out) field of the target type could be the target of a cast from the corresponding field of the source type. A pointer can be cast to int. The type type*{const-expr1}can be cast to type*{const-expr2}provided const-expr2 < const-expr1, and similarly for type@{const-expr1}and type@{const-expr2}.

An object of type datatype T.A @ can be cast to datatype T @. The current implementation isn’t quite as lenient as it should be. For example, it rejects a cast from int *{4} to $(int,int)*{2}, but this cast is safe.

For all non-pointer-containing types type, you can cast from a type ? to a char ?. This allows you to make frequent use of memcpy, memset, etc.

Why can’t I implicitly fall-through to the next switch case?

We wanted to add an explicit fallthru construct in conjunction with pattern matching, and we decided to enforce use of fallthru in all cases because this is a constant source of bugs in C code.

Do I have to initialize global variables?

You currently must provide explicit initializers for global variables that may contain pointers, so that the compiler can be sure that uninitialized memory containing pointers is not read. In the future, we expect to provide some support for initializing globals in constructor functions.

Two techniques help with initializing global arrays. First, if an array element could be 0 or NULL, the compiler will insert 0 for any elements you do not specify. For example, you can write

  int x[37];

to declare a global array x initialized with 37 elements, all 0. Second, you can use the comprehension form

  int x[37] = { for i < expr1 : expr2 }

provided that expr1 and expr2 and constant expressions. Currently, expr2 may not use the variable i, but in the future it will be able to. Note that it is not possible to have a global variable of an abstract type because it is impossible to know any constant expression of that type.

Are there threads?

Cyclone does not yet have a threads library and some of the libraries are not re-entrant. In addition, because Cyclone uses unboxed structs of three words to represent fat pointers, and updating them is not an atomic operation, it’s possible to introduce unsoundnesses by adding concurrent threads. However, in the future, we plan to provide support for threads and a static analysis for preventing these and other forms of data races.

Can I use “setjmp” and “longjmp”?

No. However, Cyclone has exceptions, which can be used for non-local control flow. The problem with setjmp and longjmp is that safety demands we prohibit a longjmp to a place no longer on the stack. A future release may have more support for non-local control flow.

What types are allowed for union members?

Currently, union members can be just about any type, other than those with kind (A) (see question on kinds, above). Examples include numeric types (including bit fields and enumerations), structs and tuples of allowable union-member types, and other unions. However, if a union contains a pointer type, you can only write the pointer, not read it. This prevents effectively casting an int to a pointer by writing an int member and then reading the pointer, for example. To use pointers as normal within a union, you must use @tagged unions.

Why can’t I do anything with values of type void?

Because we cannot know the size of an object pointed to by a pointer of type void *, we prohibit derefencing the pointer or casting it to a different pointer type. To write code that works for all pointer types, use type variables and polymorphism. Tagged unions can also substitute in some cases where void * is used in C.

What is “aprintf”?

The aprintf function is just like printf, but the output is placed in a new string allocated on the heap. Note that you can use the more general function rprintf to allocate the output in a region of your choosing.

How do I access command-line arguments?

The type of main should be

  int main(int argc, char ?? argv);

As in C, argc is the number of command-line arguments and argv[i] is a string with the ith argument. Unlike C, argv and each element of argv carry bounds information. Note that argc is redundant—it is always equal to numelts(argv).

Why can’t I pass a stack pointer to certain functions?

If the type of a function parameter is a pointer into the heap region, it cannot be passed a stack parameter. Pointer types in typedef and struct definitions refer to the heap region unless there is an explicit region annotation.

Why do I get an incomprehensible error when I assign a local’s address to a pointer variable?

If the pointer variable has a type indicating that it points into the heap, then the assignment is illegal. Try initializing the pointer variable with the local’s address, rather than delaying the assignment until later.

How much pointer arithmetic can I do?

On fat pointers, you can add or subtract an int (including via increment/decrement), as in C. It is okay for the result to be outside the bounds of the object pointed to; it is a run-time error to dereference outside of the bounds. (The compiler inserts bounds information and a run-time check; an exception is thrown if the check fails.) You can also do pointer arithmetic on zero-terminated pointers. Currently, we do not support pointer arithmetic on the other pointer types. As in C, you can subtract two pointers of the same type; the type of the result is unsigned int.

What is the type of a literal string?

The type of the string constant “foo” is const char @{4} (remember the trailing null character). However, there are implicit casts from char @{4} to char @{2}, char *{4}, and char ?, so you shouldn’t have to think too much about this.

Are strings NUL-terminated?

Cyclone follows C’s lead on this. String literals like “foo” are NUL-terminated. Many of the library functions consider a NUL character to mark the end of a string. And library functions that return strings often ensure that they are NUL terminated. However, there is no guarantee that a string is NUL terminated. For one thing, as in C, the terminating NUL may be overwritten by any character. In C this can be exploited to cause buffer overflows. To avoid this in Cyclone, strings generally have type char ?, that is, they carry bounds information. In Cyclone a string ends when a NUL character is found, or when the bounds are exceeded.

Did you just misspell NULL in the last question and answer?

No.

How do I use malloc?

malloc is a Cyclone primitive, not a library function. Currently it has an extremely restricted syntax: You must write malloc(sizeof(type)). The result has type type@, so usually there is no need to explicitly cast the result (but doing so is harmless). Usually the construct new expr is more convenient than malloc followed by initialization, but malloc can be useful for certain idioms and when porting C code.

Notice that you cannot (yet) use malloc to allocate space for arrays (as in the common idiom, malloc(n*sizeof(type)). Also, the type-checker uses a conservative analysis to ensure that the fields of the allocated space are written before they are used.

Can I call free?

Yes, but there are restrictions. If you are using a unique pointer, free will work as expected (but there are restrictions on using unique pointers). If you want to free a non-unique pointer, we have implemented free as a no-op, with type

  void free(`a::A ?);

The actual reclamation has to be done by the garbage collector or region system.

Is there a garbage collector?

Yes, we use the Boehm-Demers-Weiser conservative collector. If you don’t want to use the garbage collector (e.g., because you know that your program does little or no heap allocation), you can use the -nogc flag when linking your executable. This will make the executable smaller.

If you link against additional C code, that code must obey the usual rules for conservative garbage collection: no wild pointers and no calling malloc behind the collector’s back. Instead, you should call GC_malloc. See the collector’s documentation for more information.

Note that if you allocate all objects on the stack, garbage collection will never occur. If you allocate all objects on the stack or in regions, it is very unlikely collection will occur and nothing will actually get collected.

How can I make a stack-allocated array?

As in C, you declare a local variable with an array type. Also as in C, all uses of the variable, except as an argument to sizeof and &, are promoted to a pointer. If your declaration is

int x[256];

then uses of x have type int @`L{256} where L is the name of the block in which x is declared. (Most blocks are unnamed and the compiler just makes up a name.)

Stack-allocated arrays must be initialized when they are declared (unlike other local variables). Use an array-initializer, as in

  int y[] = { 0, 1, 2, 3 };
  int z[] = { for i < 256 : i };

To pass (a pointer to) the array to another function, the function must have a type indicating it can accept stack pointers, as explained elsewhere.

Can I use salloc or realloc?

Currently, we don’t provide support for salloc. For realloc, we do provide support, but only on heap-allocated char ? buffers.

Why do I have to cast from * to @ if I’ve already tested for NULL?

Our compiler is not as smart as you are. It does not realize that you have tested for NULL, and it insists on a check (the cast) just to be sure. You can leave the cast implicit, but the compiler will emit a warning. We are currently working to incorporate a flow analysis to omit spurious warning. Because of aliasing, threads, and undefined evaluation order, a sound analysis is non-trivial.

Why can’t a function parameter or struct field have type `a::M?

Type variables of memory kind can be instantiated with types of any size. There is no straightforward way to compile a function with an argument of arbitrary size. The obvious way to write such a function is to manipulate a pointer to the arbitrary size value instead. So your parameter should have type `a::M * or `a::M @.

Can I see how Cyclone compiles the code?

Just compile with flags -save-c and -pp. This tells the compiler to save the C code that it builds and passes to gcc, and print it out using the pretty-printer. You will have to work to make some sense out of the C code, though. It will likely contain many extern declarations (because the code has already gone through the preprocessor) and generated type definitions (because of tuples, tagged unions, and questionable pointers). Pattern-matching code gets translated to a mess of temporary variables and goto statements. Array-bounds checks and NULL checks can clutter array-intensive and pointer-intensive code. And all typedefs are expanded away before printing the output.

Can I use gdb on the output?

You can run gdb, but debugging support is not all the way there yet. By default, source-level debugging operations within gdb will reference the C code generated by the Cyclone compiler, not the Cyclone source itself. In this case, you need to be aware of three things. First, you have to know how Cyclone translates top-level identifiers to C identifiers (it prepends Cyc_ and separates namespaces by _ instead of ::) so you can set breakpoints at functions. Second, it can be hard to print values because many Cyclone types get translated to void *. Third, we do not yet have source correlation, so if you step through code, you’re stepping through C code, not Cyclone code.

To improve this situation somehwat, you can compile your files with the option -lineno. This will insert #line directives in the generated C code that refer to the original Cyclone code. This will allow you to step through the program and view the Cyclone source rather than the generated C. However, doing this has two drawbacks. First, it may occlude some operation in the generated C code that is causing your bug. Second, compilation with -lineno is significantly slower than without. Finally, the result is not bug-free; sometimes the debugger will fall behind the actual program point and print the wrong source lines; we hope to fix this problem soon.

Two more hints: First, on some architectures, the first memory allocation appears to seg fault in GC_findlimit. This is correct and documented garbage-collector behavior (it handles the signal but gdb doesn’t know that); simply continue execution. Second, a common use of gdb is to find the location of an uncaught exception. To do this, set a breakpoint at throw (a function in the Cyclone runtime).

Can I use gprof on the output?

Yes, just use the -pg flag. You should also rebuild the Cyclone libraries and the garbage collector with the -pg flag. The results of gprof make sense because a Cyclone function is compiled to a C function.

Notes for Cygwin users: First, the versions of libgmon.a we have downloaded from cygnus are wrong (every call gets counted as a self-call). We have modified libgmon.a to fix this bug, so download our version and put it in your cygwin/lib directory. Second, timing information should be ignored because gprof is only sampling 100 or 1000 times a second (because it is launching threads instead of using native Windows profiling). Neither of these problems are Cyclone-specific.

Is there an Emacs mode for Cyclone?

Sort of. In the doc/ directory of the distribution you will find a font-lock.el file and elisp code (in cyclonedotemacs.el) suitable for inclusion in your .emacs file. However, these files change C++ mode and use it for Cyclone rather than creating a new Cyclone mode. Of course, we intend to make our own mode rather than destroy C++-mode’s ability to be good for C++. Note that we have not changed the C++ indentation rules at all; our elisp code is useful only for syntax highlighting.

Does Cyclone have something to do with runtime code generation?

Cyclone has its roots in Popcorn, a language which was safe but not as compatible with C. An offshoot of Popcorn added safe runtime code generation, and was called Cyclone. The current Cyclone language is a merger of the two, refocused on safety and C compatibility. Currently, the language does not have support for runtime code generation.

What platforms are supported?

You need a platform that has gcc, GNU make, ar, sed, either bash or ksh, and the ability to build the Boehm-Demers-Weiser garbage collector. Furthermore, the size of int and all C pointers must be the same. We actively develop Cyclone in Cygwin (a Unix emulation layer for Windows 98, NT, 2K), Linux, and Mac OS X. Versions have run on OpenBSD and FreeBSD.

Why aren’t there more libraries?

We are eager to have a wider code base, but we are compiler writers with limited resources. Let us know of useful code you write.

Why doesn’t List::imp_rev(l) change l to its reverse?

The library function List::imp_rev mutates its argument by reversing the tl fields. It returns a pointer to the new first cell (the old last cell), but l still points to the old first cell (the new last cell).

Can I inline functions?

Functions can be declared inline as in ISO C99. You can get additional inlining by compiling the Cyclone output with the -O2 flag. Whether a function is inlined or not has no effect on Cyclone type-checking.

If Cyclone is safe, why does my program crash?

There are certain classes of errors that Cyclone does not attempt to prevent. Two examples are stack overflow and various numeric traps, such as division-by-zero. It is also possible to run out of memory. Other crashes could be due to compiler bugs or linking against buggy C code (or linking incorrectly against C code).

Note that when using gdb, it may appear there is a seg fault in GC_findlimit(). This behavior is correct; simply continue execution.

What are compile-time constants?

Cyclone’s compile-time constants are NULL, integer and character constants, and arithmetic operations over compile-time constants. Unlike C, sizeof(t) is not an integral constant expression in our current implementation of Cyclone because our compiler does not know the actual size of aggregate types; we hope to repair this in a future version. Constructs requiring compile-time constants are: tuple-subscript (e.g., x[3] for tuple x), sizes in array declarations (e.g., int y[37]), and sizes in pointer bounds (e.g., int * x{124}).

How can I get the size of an array?

If expr is an array, then numelts(expr) returns the number of elements in the array. If expr is a pointer to an array, numelts(expr) returns the number of elements in the array pointed to. If expr is a fat pointer, then the number of elements is calculated at runtime from the bounds information contained in the fat pointer. For other types, the size is determined at compile-time.